[pull] main from MetaMask:main#632
Merged
Merged
Conversation
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> Replace deprecated `Button` usage with DSRN package. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/DSYS-445 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** https://github.com/user-attachments/assets/8cc0ccb2-5b79-4f37-8d8f-ae326849716d ### **After** https://github.com/user-attachments/assets/e42afbad-ebe8-41a6-b80d-2bcb0964358e ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches critical onboarding/login entry points by swapping button components and props; risk is mainly UI/interaction regressions (disabled/loading/accessibility) rather than logic changes. > > **Overview** > Migrates several onboarding/authentication screens from the deprecated component-library `Button` to the design-system `Button` (DSRN), updating props (`variant`, `isFullWidth`, `isDisabled`, `isLoading`) and switching to children-based labels. > > Link-style buttons that aren’t yet migrated remain on the old component (`OldButton`). Tests and snapshots are updated accordingly, including switching assertions to `toBeDisabled()`/`toBeEnabled()` and reflecting the new button render/accessibility structure. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 40ba4e7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## **Description**
PUMP TP/SL price inputs were blocked at 5 decimal places due to two
independent hardcoded limits. PUMP trades at ~$0.00186, so valid trigger
prices require 6 decimal places (e.g. `0.001234`).
**Root cause (two layers):**
1. `PerpsTPSLView` passed `decimals={TP_SL_VIEW_CONFIG.KeypadDecimals}`
(hardcoded `5`) to `<Keypad>` — the keypad rule silently dropped the 6th
digit
2. `usePerpsTPSLForm` called `hasExceededSignificantFigures(sanitized)`
with default `maxSigFigs=5` — `countSignificantFigures("0.001234")`
returns 6 (counts all decimal digits including leading zeros), blocking
the state update
**Fix:** Replace both hardcoded limits with values from
`DECIMAL_PRECISION_CONFIG`:
- `keypadDecimals` is now computed dynamically from `currentPrice`:
`floor(-log10(price)) + MaxSignificantFigures`, clamped to `[2,
MaxPriceDecimals]`
- `hasExceededSignificantFigures` now uses `MaxPriceDecimals` (=6) as
the limit in both TP and SL handlers
## **Changelog**
CHANGELOG entry: Fixed TP/SL trigger price input for low-price assets
(e.g. PUMP) now accepting up to 6 decimal places
## **Related issues**
Fixes:
[TAT-2403](https://consensyssoftware.atlassian.net/browse/TAT-2403)
## **Manual testing steps**
```gherkin
Feature: PUMP TP/SL trigger price decimal precision
Scenario: User can enter a 6-decimal TP price for PUMP
Given I have an open PUMP long position
When I navigate to the TP/SL screen
And I focus the Take Profit price input
And I type "0.001234" via the keypad
Then the Take Profit price input shows "0.001234"
```
## **Screenshots/Recordings**
### **Before**
https://github.com/user-attachments/assets/cd159c4c-f999-47b8-9c37-dc93dcb79ed2
### **After**
<!-- [screenshots/recordings] -->
https://github.com/user-attachments/assets/c401f733-53b9-4c62-ae9e-6523cf7ac43c
## **Pre-merge author checklist**
- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
## **Validation Recipe**
<details>
<summary>Automated validation recipe (validate-recipe.sh)</summary>
```json
{
"pr": "27901",
"title": "PUMP TP/SL trigger price accepts up to 6 decimal places",
"jira": "TAT-2403",
"acceptance_criteria": [
"TP/SL trigger price input for PUMP accepts up to 6 decimal places (e.g. 0.001234)",
"Decimal precision is dynamically driven by market price — not a hardcoded global constant",
"Other markets (e.g. BTC) are unaffected — basic TP/SL via presets still works"
],
"validate": {
"static": ["yarn lint:tsc"],
"runtime": {
"pre_conditions": ["wallet.unlocked"],
"steps": [
{ "id": "open_pump_position", "action": "flow_ref", "ref": "trade-open-market",
"params": { "symbol": "PUMP", "side": "long", "usdAmount": "11" } },
{ "id": "wait_position_fill", "action": "wait_for",
"expression": "Engine.context.PerpsController.getPositions().then(function(ps){var p=ps.filter(function(x){return x.symbol==='PUMP'});return JSON.stringify({count:p.length})})",
"assert": { "operator": "gt", "field": "count", "value": 0 },
"timeout_ms": 20000, "poll_ms": 1000 },
{ "id": "create_tpsl_preset", "action": "flow_ref", "ref": "tpsl-create",
"params": { "symbol": "PUMP", "tpPreset": "25", "slPreset": "-10" } },
{ "id": "nav_tpsl_for_6dec_test", "action": "navigate", "target": "PerpsTPSL",
"params": { "asset": "PUMP", "currentPrice": 0.00185, "direction": "long" } },
{ "id": "wait_tpsl_screen", "action": "wait_for", "route": "PerpsTPSL" },
{ "id": "focus_tp_input", "action": "eval_sync",
"expression": "(function(){var hook=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;var found=null;function walk(f){if(!f)return;var props=f.memoizedProps;if(props&&props.testID===\"perps-tpsl-tp-input\"){found=f;return;}walk(f.child);if(!found)walk(f.sibling);}if(hook&&hook.renderers){hook.renderers.forEach(function(v,k){var roots=hook.getFiberRoots?hook.getFiberRoots(k):null;if(roots)roots.forEach(function(r){if(!found)walk(r.current);});})}if(!found)return \"not-found\";var cur=found.child;while(cur){if(cur.tag===5&&cur.stateNode){var pub=cur.stateNode.canonical&&cur.stateNode.canonical.publicInstance;if(pub&&pub.focus){pub.focus();return \"focused\";}return \"no-focus-method\";}cur=cur.child;}return \"no-host\";})()",
"assert": { "operator": "eq", "value": "focused" } },
{ "id": "clear_tp_keypad", "action": "clear_keypad", "count": 8 },
{ "id": "type_6decimal_tp_price", "action": "type_keypad", "value": "0.001234" },
{ "id": "assert_tp_6decimal_value", "action": "eval_sync",
"expression": "(function(){var hook=globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;var found=null;function walk(f){if(!f)return;var props=f.memoizedProps;if(props&&props.testID===\"perps-tpsl-tp-input\"){found=f;return;}walk(f.child);if(!found)walk(f.sibling);}if(hook&&hook.renderers){hook.renderers.forEach(function(v,k){var roots=hook.getFiberRoots?hook.getFiberRoots(k):null;if(roots)roots.forEach(function(r){if(!found)walk(r.current);});})}if(!found)return JSON.stringify({v:\"not-found\"});return JSON.stringify({v:found.memoizedProps&&found.memoizedProps.value||\"no-value\"});})()",
"assert": { "field": "v", "operator": "contains", "value": "001234" } },
{ "id": "check_no_blocking_errors", "action": "log_watch",
"window_seconds": 3, "must_not_appear": ["TypeError", "RangeError"] },
{ "id": "screenshot_6decimals", "action": "screenshot", "filename": "pump-tpsl-6-decimals.png" },
{ "id": "cleanup_close_pump", "action": "flow_ref", "ref": "trade-close-position",
"params": { "symbol": "PUMP" } }
]
}
}
}
```
</details>
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk UI/input-validation change limited to TP/SL price entry and
rounding; main risk is unintended precision/rounding differences for
some markets when entering prices via keypad or percentage conversions.
>
> **Overview**
> Fixes TP/SL trigger price entry for very low-priced perps assets by
**removing hardcoded 5-decimal limits**.
>
> `PerpsTPSLView` now computes `keypadDecimals` dynamically from
`currentPrice` (clamped by `DECIMAL_PRECISION_CONFIG`) and only applies
it to *price* inputs (percentage inputs keep existing precision).
`usePerpsTPSLForm` updates price validation and all RoE→price rounding
to use `DECIMAL_PRECISION_CONFIG.MaxPriceDecimals`, and adds tests
ensuring 6-decimal TP/SL prices (e.g. `0.001234`) are accepted.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
620a45c. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## **Description** `URNM` (Sprott Uranium Miners ETF) was listed as `'equity'` in `HIP3_ASSET_MARKET_TYPES` when it should be `'commodity'`, and `USAR` (US equity fund) was listed as `'commodity'` when it should be `'equity'`. This caused both instruments to appear under the wrong filter tab and display the wrong badge. Fixed by correcting the two values in the config constant — the single source of truth for HIP-3 market classification. ## **Changelog** CHANGELOG entry: Fixed incorrect market category assignments for URNM (now Commodity) and USAR (now Stock) in the Perps market list. ## **Related issues** Fixes: [TAT-2499](https://consensyssoftware.atlassian.net/browse/TAT-2499) ## **Manual testing steps** ```gherkin Feature: Market category filter tabs Scenario: URNM appears under Commodities Given I am on the Perps market list When I tap the "Commodities" filter tab Then URNM should appear in the list with a COMMODITY badge Scenario: USAR appears under Stocks Given I am on the Perps market list When I tap the "Stocks" filter tab Then USAR should appear in the list with a STOCK badge Scenario: URNM does not appear under Stocks Given I am on the Perps market list When I tap the "Stocks" filter tab Then URNM should NOT be visible Scenario: USAR does not appear under Commodities Given I am on the Perps market list When I tap the "Commodities" filter tab Then USAR should NOT be visible ``` ## **Screenshots/Recordings** ### **Before** See .task/fix/tat-2499-0325-1917/artifacts/before.mp4 ### **After** See .task/fix/tat-2499-0325-1917/artifacts/after.mp4 ## **Validation Recipe** <details> <summary>Automated validation recipe (validate-recipe.sh)</summary> ```json { "pr": "27910", "title": "URNM and USAR market categories corrected", "jira": "TAT-2499", "acceptance_criteria": [ "URNM is categorised as commodity (not equity)", "USAR is categorised as equity/stock (not commodity)", "Fix is at the config layer, not UI layer" ], "validate": { "static": ["yarn lint:tsc"], "runtime": { "pre_conditions": ["wallet.unlocked", "perps.feature_enabled"], "steps": [ { "id": "nav-market-list", "description": "Navigate to market trending list", "action": "navigate", "target": "PerpsTrendingView" }, { "id": "wait-market-list", "description": "Wait for market list route to be active", "action": "wait_for", "route": "PerpsTrendingView" }, { "id": "assert-urnm-is-commodity", "description": "Assert URNM has marketType=commodity in controller state (the fix)", "action": "eval_async", "expression": "Engine.context.PerpsController.getMarketDataWithPrices().then(function(markets){var urnm=markets.find(function(market){return market.symbol==='xyz:URNM'});return JSON.stringify({found:!!urnm,marketType:urnm?urnm.marketType:null})})", "assert": { "operator": "eq", "field": "marketType", "value": "commodity" } }, { "id": "assert-usar-is-equity", "description": "Assert USAR has marketType=equity in controller state (the fix)", "action": "eval_async", "expression": "Engine.context.PerpsController.getMarketDataWithPrices().then(function(markets){var usar=markets.find(function(market){return market.symbol==='xyz:USAR'});return JSON.stringify({found:!!usar,marketType:usar?usar.marketType:null})})", "assert": { "operator": "eq", "field": "marketType", "value": "equity" } }, { "id": "nav-commodities-filter", "description": "Select Commodities filter tab to verify URNM appears there", "action": "flow_ref", "ref": "market-discovery", "params": { "symbol": "xyz:URNM", "category": "commodities" } }, { "id": "assert-urnm-badge-commodity", "description": "Verify URNM badge shows commodity type in market detail", "action": "eval_async", "expression": "Engine.context.PerpsController.getMarketDataWithPrices().then(function(markets){var urnm=markets.find(function(market){return market.symbol==='xyz:URNM'});return JSON.stringify({found:!!urnm,marketType:urnm?urnm.marketType:null,isHip3:urnm?urnm.isHip3:null})})", "assert": { "operator": "eq", "field": "marketType", "value": "commodity" } }, { "id": "nav-stocks-filter", "description": "Select Stocks filter tab to verify USAR appears there", "action": "flow_ref", "ref": "market-discovery", "params": { "symbol": "xyz:USAR", "category": "stocks" } }, { "id": "assert-usar-badge-equity", "description": "Verify USAR badge shows equity type in market detail", "action": "eval_async", "expression": "Engine.context.PerpsController.getMarketDataWithPrices().then(function(markets){var usar=markets.find(function(market){return market.symbol==='xyz:USAR'});return JSON.stringify({found:!!usar,marketType:usar?usar.marketType:null,isHip3:usar?usar.isHip3:null})})", "assert": { "operator": "eq", "field": "marketType", "value": "equity" } }, { "id": "screenshot-final", "description": "Capture final state for human review", "action": "screenshot", "filename": "after-fix-usar-in-stocks" } ] } } } ``` </details> ## **Pre-merge author checklist** - [x] I've followed MetaMask Contributor Docs and Coding Standards - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [ ] I've documented my code using JSDoc format if applicable - [x] I've applied the right labels on the PR ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: updates two static `HIP3_ASSET_MARKET_TYPES` entries and adds a small unit test to prevent regressions; no runtime logic or security-sensitive code changes. > > **Overview** > Fixes HIP-3 perps market classification by swapping the `HIP3_ASSET_MARKET_TYPES` mappings for `xyz:URNM` (now `commodity`) and `xyz:USAR` (now `equity`), which affects filter tabs and badges. > > Adds `hyperLiquidConfig.test.ts` to assert correct market-type mappings for these symbols and a few representative equity/commodity/forex entries. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 066edfa. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Arthur Breton <abreton@siteed.net>
…n + NFT + Hooks + Misc + perps (#27885) ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> Part of the analytics migration workstream ([#26686](#26686)) that replaces the legacy `useMetrics` hook with the new `useAnalytics` hook across the codebase. This PR (C4) migrates ~18 files covering Simulation Details, NFT display, Wallet Actions, hooks, and misc components, plus one perps test-only file. **Changes per file:** - Replace `useMetrics` import/call with `useAnalytics` - Update `MetaMetricsEvents` import paths from `hooks/useMetrics` → `core/Analytics` - Rename `addTraitsToUser` → `identify` in `ShowDisplayNFTMediaSheet` (piggyback rename per workstream plan) - In tests: replace hand-rolled mock objects with `createMockUseAnalyticsHook`, use `jest.mocked()` instead of `as jest.Mock` / `as never` - Update `MetricsEventBuilder` references to `AnalyticsEventBuilder` where applicable ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: #26815 Refs: #26686 ## **Manual testing steps** N/A ## **Screenshots/Recordings** ### **Before** N/A ### **After** N/A ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- Generated with the help of the pr-description AI skill --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Mostly a mechanical analytics refactor, but it touches user flows (wallet reset, basic functionality, ramps, NFT detection, simulation confirmation) where incorrect hook behavior could change tracking/consent flags or event emission. > > **Overview** > Migrates several components and hooks from legacy `useMetrics` to the new `useAnalytics` hook, keeping existing event tracking calls but updating imports/usages (e.g., `MetaMetricsEvents` paths and `AnalyticsEventBuilder` in tests). > > Updates tests to mock `useAnalytics` via `createMockUseAnalyticsHook`/`jest.mocked`, and switches the NFT media consent sheet from `addTraitsToUser` to `identify` when recording user traits. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 59b8a7a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…27923) ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> Part of the analytics C5 migration phase: replaces `useMetrics` with the new `useAnalytics` hook in `PredictGTMModal`. The `useMetrics` hook is being deprecated in favour of `useAnalytics`, which provides the same `trackEvent` / `createEventBuilder` API through a unified interface. This commit updates the component and its unit test to use the new hook and the `createMockUseAnalyticsHook` test helper. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: #27883 ## **Manual testing steps** N/A — pure internal refactor with no user-facing behaviour change. Existing unit tests cover the analytics event firing. ## **Screenshots/Recordings** ### **Before** N/A ### **After** N/A ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- Generated with the help of the pr-description AI skill --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk internal refactor that swaps the analytics hook used by `PredictGTMModal`; behavior should be unchanged aside from potential wiring/mocking issues in event tracking. > > **Overview** > Updates `PredictGTMModal` to use the new `useAnalytics` hook instead of `useMetrics` while keeping the same `trackEvent`/`createEventBuilder` flow for the modal’s analytics events. > > Refactors `PredictGTMModal.test.tsx` accordingly by mocking `useAnalytics` and using the shared `createMockUseAnalyticsHook` helper, plus minor jest mocking cleanup for `StorageWrapper.getItem`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 55c10d9. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…e after SL execution (#27906) ## **Description** Multi-fill trades (e.g., stop-loss orders split across multiple price levels by HyperLiquid) showed incorrect PnL and order size on the Perp Market screen and Perps Home screen. The deduplication key `orderId-timestamp` used to merge REST and WebSocket fills collapsed distinct fills with the same orderId+timestamp into one entry, losing size/PnL data. Fixed by extending the dedup key to `orderId-timestamp-size-price`, which preserves all distinct fills while still deduplicating identical entries from both sources. The Activity page was already correct (no Map-based dedup). ## **Changelog** CHANGELOG entry: Fixed incorrect PnL and order size for multi-fill trades on the Perp Market screen and Home screen recent activity ## **Related issues** Fixes: [TAT-2483](https://consensyssoftware.atlassian.net/browse/TAT-2483) ## **Manual testing steps** ```gherkin Feature: Multi-fill trade aggregation consistency Scenario: SL execution with multiple fills shows correct aggregated values Given the user has an account with SL trades that executed as multiple fills When the user navigates to the ETH market page trades tab Then the PnL and order size match the Activity page values And the PnL and order size match the HyperLiquid UI aggregated view ``` ## **Screenshots/Recordings** ### **Before** ### **After** ## **Validation Recipe** <details> <summary>Automated validation recipe (validate-recipe.sh)</summary> ```json { "pr": "27906", "title": "Verify aggregated PnL and order size consistency across all trade history surfaces", "jira": "TAT-2483", "acceptance_criteria": [ "Perp Market screen displays correct aggregated PnL and order size for multi-fill trades", "Perps Home screen recent activity shows identical aggregated values", "Activity page shows the same aggregated values as the other two surfaces", "Multi-fill trades (same orderId + timestamp) are properly aggregated, not collapsed by dedup" ], "validate": { "static": ["yarn lint:tsc"], "runtime": { "pre_conditions": ["wallet.unlocked", "perps.feature_enabled"], "steps": [ { "id": "check_multi_fills_exist", "description": "Verify account has multi-fill trades (prerequisite for the bug)", "action": "eval_async", "expression": "Engine.context.PerpsController.getActiveProviderOrNull().getOrderFills({aggregateByTime: false}).then(function(fills) { var grouped = {}; fills.forEach(function(f) { var key = f.orderId + '_' + f.timestamp; if (!grouped[key]) grouped[key] = 0; grouped[key] = grouped[key] + 1; }); var multiCount = 0; Object.keys(grouped).forEach(function(k) { if (grouped[k] > 1) multiCount = multiCount + 1; }); return JSON.stringify({totalFills: fills.length, multiFillKeys: multiCount}); })", "assert": { "operator": "gt", "field": "multiFillKeys", "value": 0 } }, { "id": "verify_new_dedup_preserves_fills", "description": "Assert that the fixed dedup key (orderId+timestamp+size+price) preserves all fills - this fails with old key", "action": "eval_async", "expression": "Engine.context.PerpsController.getActiveProviderOrNull().getOrderFills({aggregateByTime: false}).then(function(fills) { var oldKeyMap = {}; var newKeyMap = {}; fills.forEach(function(f) { var oldKey = f.orderId + '_' + f.timestamp; var newKey = f.orderId + '_' + f.timestamp + '_' + f.size + '_' + f.price; oldKeyMap[oldKey] = f; newKeyMap[newKey] = f; }); var oldCount = Object.keys(oldKeyMap).length; var newCount = Object.keys(newKeyMap).length; return JSON.stringify({totalFills: fills.length, oldKeyUnique: oldCount, newKeyUnique: newCount, oldKeyLost: fills.length - oldCount, newKeyLost: fills.length - newCount}); })", "assert": { "operator": "eq", "field": "newKeyLost", "value": 0 } }, { "id": "nav_market_eth", "description": "Navigate to ETH market page (has multi-fill SL trades)", "action": "flow_ref", "ref": "market-discovery", "params": { "symbol": "ETH" } }, { "id": "wait_market_render", "description": "Wait for trades list to render with REST fills", "action": "wait", "ms": 5000 }, { "id": "screenshot_market", "description": "Capture market trades list for visual review", "action": "screenshot", "filename": "market-trades-eth" }, { "id": "nav_activity", "description": "Navigate to activity page trades tab", "action": "flow_ref", "ref": "activity-view", "params": { "tab": "trades" } }, { "id": "wait_activity_render", "description": "Wait for activity data to load", "action": "wait", "ms": 3000 }, { "id": "screenshot_activity", "description": "Capture activity trades for visual comparison", "action": "screenshot", "filename": "activity-trades" } ] } } } ``` </details> ## **Pre-merge author checklist** - [x] I've followed MetaMask Contributor Docs and Coding Standards - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [ ] I've documented my code using JSDoc format if applicable - [x] I've applied the right labels on the PR ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes fill deduplication logic used to compute displayed trade history, which can affect user-visible PnL/size calculations if the new composite key has edge cases (e.g., precision/format differences for `size`/`price`). Scope is limited to Perps Home and Market fills merging plus added tests. > > **Overview** > Fixes incorrect PnL and order size for stop-loss (multi-fill) executions by changing REST+WebSocket fill deduplication to key on `orderId`+`timestamp`+`size`+`price` (instead of just `orderId`+`timestamp`), so distinct fills at the same timestamp are no longer collapsed. > > Adds/updates unit tests for `usePerpsHomeData` and `usePerpsMarketFills` to cover multi-fill preservation and to assert WebSocket data still wins for *exact* duplicates across sources. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 68d15e8. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…laced components (#27652) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Upgrades `@metamask/design-system-react-native` from `^0.10.0` to `^0.11.0` and deprecates local component-library components that now have design system replacements. https://github.com/MetaMask/metamask-design-system/releases/tag/v25.0.0 **Why:** The design system monorepo v25.0.0 release (`@metamask/design-system-react-native@0.11.0`) shipped several new components that duplicate functionality currently in `app/component-library/`. Upgrading and marking local equivalents as `@deprecated` guides developers toward the canonical design system implementations and prevents further adoption of the local versions. **What changed:** 1. **Package upgrade** — `@metamask/design-system-react-native` bumped from `^0.10.0` to `^0.11.0` 2. **Breaking change migration** — `ButtonIcon` `isFloating` boolean prop replaced by `variant` enum in 0.11.0; migrated `InputStepper` to use `ButtonIconVariant.Floating` 3. **10 components deprecated** with `@deprecated` JSDoc following the existing codebase pattern, linking to component READMEs and [migration docs](https://github.com/MetaMask/metamask-design-system/releases): - `MainActionButton` (components-temp) - `TabEmptyState` (components-temp) - `ButtonFilter` (components-temp) - `BannerBase` (foundation) - `Banner` (union — use `BannerAlert` from DS directly) - `BannerAlert` (variant) - `BannerTip` (variant — unused, will be removed) - `BottomSheet` - `BottomSheetDialog` (foundation) - `ListItem` ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: ## **Manual testing steps** N/A — deprecation annotations are documentation-only changes. The `ButtonIcon` prop migration in `InputStepper` can be verified by navigating to the Bridge input stepper UI. ## **Screenshots/Recordings** ### **Before** N/A ### **After** N/A ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Upgrades a shared UI dependency and migrates to a changed `ButtonIcon` API, which could cause subtle UI/regression issues if other call sites rely on previous props or styles. Password handling was also adjusted to be controlled in a couple of sensitive SRP/password-entry screens, so review for unintended behavior changes. > > **Overview** > Upgrades `@metamask/design-system-react-native` to `0.11.0` (and `@metamask/design-system-shared` to `0.4.0` via lockfile), and updates the Bridge `InputStepper` to the new `ButtonIcon` API by replacing `isFloating` with `variant={ButtonIconVariant.Floating}`. > > Marks multiple in-repo component-library equivalents as **deprecated** via JSDoc (e.g. `Banner`, `BannerBase`, `BottomSheet`, `ListItem`, and several `components-temp` components), pointing developers to the design-system replacements and noting `BannerVariant.Tip`/`BannerTip` as unused and slated for removal. > > Tightens password input control by initializing `ManualBackupStep1` password state to `''` and wiring `RevealPrivateCredential`’s `PasswordEntry` to a new required `password` prop, with corresponding snapshot updates. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5987809. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## **Description**
Update chart URL
## **Changelog**
<!--
If this PR is not End-User-Facing and should not show up in the
CHANGELOG, you can choose to either:
1. Write `CHANGELOG entry: null`
2. Label with `no-changelog`
If this PR is End-User-Facing, please write a short User-Facing
description in the past tense like:
`CHANGELOG entry: Added a new tab for users to see their NFTs`
`CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`
(This helps the Release Engineer do their job more quickly and
accurately)
-->
CHANGELOG entry: null
## **Related issues**
Fixes:
## **Manual testing steps**
```gherkin
Feature: my feature name
Scenario: user [verb for user action]
Given [describe expected initial app state]
When user [verb for user action]
Then [describe expected outcome]
```
## **Screenshots/Recordings**
<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->
### **Before**
<!-- [screenshots/recordings] -->
### **After**
<!-- [screenshots/recordings] -->
## **Pre-merge author checklist**
- [ ] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I've included tests if applicable
- [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
## **Pre-merge reviewer checklist**
- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk config-only change, but it affects where the in-app advanced
charts load their static assets, so a bad URL would break chart
rendering across builds.
>
> **Overview**
> Updates `MM_CHARTING_LIBRARY_URL` in `builds.yml` to point to the new
hosted TradingView Advanced Charts asset location
(`charting-assets.static.metamask.io/.../v30.1.0/`) instead of the
previous S3 URL.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
31b605a. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Added 'View All' button for all sections in the Explore page <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: added 'View All' button for all sections in the Explore page ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/ASSETS-2705 ## **Manual testing steps** - Navigate to the explore page - Search for anything like "e" - You should see each section showing a "View all" button - When clicking on them it should navigate to a screen where it filters by that query ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before & After** https://github.com/user-attachments/assets/50fa43a9-7771-4305-9f2f-f4e6399020b8 ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a new navigation route/screen and changes Explore search result rendering and analytics instrumentation, which could affect navigation flows and event reporting. Risk is moderate since it’s UI/metrics focused but touches shared section config and touch/scroll handling. > > **Overview** > Adds a **"View all"** affordance to Explore search section headers (shown when a section has more than 3 results), limits the inline section preview to 3 items, and navigates to a new `ExploreSectionResultsFullView` screen that renders the full section results. > > Introduces shared Explore search analytics utilities (`TapView`, `TrackedRowItem`, `useScrollTracking`, `trackExploreEvent`) and a new MetaMetrics event (`EXPLORE_SEARCH_INTERACTED`) to track taps and first-scroll interactions in both the search results list and the new full-results view. > > Updates navigation/types to register the new route (`Routes.EXPLORE_SECTION_RESULTS_FULL_VIEW`, `RootStackParamList`) and extends `SECTIONS_CONFIG` with a required `getItemIdentifier` for consistent item-level analytics; adds/updates unit tests and snapshots accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 360771b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
… support (#27895) ## Summary - Expand `gasless-swap.spec.ts` with three test scenarios: - **ETH → MUSD** (gasless of native token, `gasIncluded: true`) - **USDC → MUSD** (gasless with ERC-20 approval, `gasIncluded: true`) - **ETH → MUSD via 7702** (`gasIncluded7702: true`) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Medium risk because it monkey-patches `mockttp` matching behavior to suppress `Error('Aborted')` and expands E2E mocking/fixtures; mistakes could hide real errors or affect unrelated tests. > > **Overview** > Adds new gasless swap smoke test scenarios for `MUSD`, including a live (non-skipped) `ETH → MUSD` flow using `gasIncluded7702`, plus two additional (currently skipped) cases for standard gasless `ETH → MUSD` and `USDC → MUSD` with approval. > > Extends swap test fixtures/mocks to support `MUSD` (token lists, tokens API response, spot prices) and introduces dedicated quote fixtures for gasless and 7702 quotes (including `txFee` fields required by validators/UI). > > Hardens E2E mocking by patching `mockttp`’s `matchesAll` to treat `Error('Aborted')` as a non-match, preventing aborted client requests from surfacing as unhandled rejections in Jest, and simplifies swap activity checking by removing an explicit post-toast delay. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit a0acb80. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…7971) This is mostly about implementing the positions section on the ondo campaign details page, but it also has some changes to the leaderboard components. ## **Jira** [RWDS-1102](http://consensyssoftware.atlassian.net/browse/RWDS-1102) ## **Changelog** CHANGELOG entry: Ondo GM campaign portfolio positions ## **Screenshots/Recordings** - No leaderboard position and positions (cta takes them to rwa token page) <img width="1011" height="1942" alt="Screenshot from 2026-03-26 13-28-38" src="https://github.com/user-attachments/assets/19f3b1b9-8344-46d5-98a8-aa0bf5c37591" /> - Error loading positions section <img width="1011" height="1942" alt="Screenshot from 2026-03-26 13-20-08" src="https://github.com/user-attachments/assets/03905a25-8096-4f91-93b4-bff89884a701" /> - Positions loaded, tapping them takes a user to the rwa details page for that token/network & notice last updated at. <img width="1031" height="1641" alt="Screenshot from 2026-03-26 12-11-03" src="https://github.com/user-attachments/assets/a03fff0f-ad66-43a4-ada8-c54275539689" /> - Leaderboard rank/position component (no more card layout) <img width="934" height="1557" alt="Screenshot from 2026-03-26 12-11-12" src="https://github.com/user-attachments/assets/873747e5-9aeb-46f3-bcef-a6bfd7b252cd" /> <img width="1031" height="1641" alt="Screenshot from 2026-03-26 12-11-03" src="https://github.com/user-attachments/assets/3faf3f05-9181-4c56-8392-5d04a6107d20" /> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a new authenticated portfolio endpoint, cache/state plumbing, and new UI on the Ondo campaign details screen; moderate risk due to new data flow and cache invalidation paths in `RewardsController` and Redux state. > > **Overview** > Adds an **Ondo GM portfolio “Your Positions”** section to the Ondo campaign details page (opted-in users only), including loading/error/empty states and navigation to RWA token list or specific asset details. > > Extends rewards data flow to fetch/cache portfolio positions: introduces `useGetOndoPortfolioPosition`, Redux state/actions/selectors for `ondoCampaignPortfolio`, and a new `RewardsController:getOndoCampaignPortfolioPosition` action backed by a new authenticated data-service endpoint `/ondo-gm/:campaignId/portfolio/me`, with cache invalidation on opt-in/logout/subscription cache invalidation. > > Refactors Ondo campaign/leaderboard payloads and UI: switches multiple DTO fields from snake_case to camelCase, adds `isLeaderboardNotYetComputed` (404) handling with an info banner, updates `CampaignHowItWorks` to use flat `steps` instead of phased data, and tightens `CampaignTile` participant-status fetching to active Ondo campaigns only. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0e4fc3e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: sophieqgu <sophieqgu@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
#27981) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Adds **wallet balance context** to the Card **`CARD_VIEWED`** event when the user opens the **Spending limit** (or enable-token) flow, and aligns row affordance icons with a **down** chevron on the settings-style rows. **Why**: Product and analytics need to understand **Linea mUSD exposure**, which **card-supported asset** has the highest fiat balance for the current account, and which asset is **top across the whole wallet** (EVM networks plus Solana) at screen view time—without inferring this from other screens. **What changed**: - **`useSpendingLimit.ts`** - Calls **`useTokensWithBalance`** twice: **card chain IDs** (`CARD_CHAIN_IDS`) for `walletTokens`, and **all configured EVM chain IDs plus Solana** (via **`selectEvmNetworkConfigurationsByChainId`** and **`cardNetworkInfos.solana.caipChainId`**) for `allWalletTokens`. - On **`MetaMetricsEvents.CARD_VIEWED`**, adds: - **`musd_linea_balance`**: fiat amount for **mUSD on Linea** on the selected account, or **0** if absent. - **`top_card_chain_asset`**: `network:symbol` (e.g. `linea:musd`) for the **highest fiat** token among **card-supported** balances (`allTokens` intersection), or **`null`** if none / unsupported-only (e.g. top balance not in card list). - **`top_wallet_chain_asset`** / **`top_wallet_asset_balance`**: highest fiat token across **all wallet tokens** and its fiat value (or **`null`** / **0** when applicable). Network label uses **`caipChainIdToNetwork`** where mapped, otherwise the CAIP chain id string. - Introduces **`toNetworkAsset`** to normalize **`network:symbol`** for analytics. - Analytics effect runs after wallet token hooks; dependency array intentionally limited (with eslint comment) so the viewed snapshot stays stable at mount. - **`SpendingLimit.tsx`**: **Account**, **Token**, and **Spending limit** row trailing icons **`ArrowRight` → `ArrowDown`** for clearer expand/dropdown affordance. - **`useSpendingLimit.test.ts`**: Tests for **`musd_linea_balance`**, **`top_card_chain_asset`** (including empty list, non-card-supported top token), and **`top_wallet_chain_asset`** / **`top_wallet_asset_balance`**; clarifies default selector mock comment. ## **Changelog** CHANGELOG entry: Card Spending limit screen analytics include Linea mUSD fiat balance, top card-supported asset by fiat, and top wallet-wide asset by fiat when the screen is viewed. ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: Spending limit viewed analytics and row icons Scenario: CARD_VIEWED includes balance properties Given I open Spending limit (or enable flow) with a known Linea mUSD balance and other tokens When the screen is tracked as viewed Then CARD_VIEWED includes musd_linea_balance, top_card_chain_asset, top_wallet_chain_asset, and top_wallet_asset_balance consistent with the selected account and supported tokens Scenario: Row chevrons When I view the Spending limit settings rows (Account, Token, Spending limit) Then each row shows a down chevron affordance instead of a right arrow ``` ## **Screenshots/Recordings** Optional: capture the three rows showing **down** chevrons. For analytics, verify in debug/logs or analytics tooling that **`CARD_VIEWED`** payloads include the new properties. ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds new analytics properties derived from wallet token balances and network configuration, including multi-chain sorting logic and a one-time firing guard; mistakes could skew metrics or add minor performance overhead on screen mount. > > **Overview** > **Enriches the card Spending Limit/Enable flow `CARD_VIEWED` analytics event** with wallet balance context: `musd_linea_balance`, the highest-fiat *card-supported* asset (`top_card_chain_asset`), and the highest-fiat asset across all configured wallet chains plus Solana (`top_wallet_chain_asset` + `top_wallet_asset_balance`). > > Updates `useSpendingLimit` to fetch balances via `useTokensWithBalance` for both card chains and all wallet chains, normalizes `network:symbol` formatting, and delays/fires the screen-view event **exactly once** after `allTokens` is non-empty to avoid unsupported/empty snapshots; adds unit tests covering these new metrics. > > Adjusts the Spending Limit settings rows’ trailing affordance icons from `ArrowRight` to `ArrowDown` for Account/Token/Spending limit rows. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f1b5724. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Enforces the previously defined `LIMIT_SESSIONS` limit for WCv2 connections. When this limit (20 connections) is exceeded, the oldest connection is dropped. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/WAPI-1356 ## **Manual testing steps** 1. Modify this constant to be 2 2. Use the ios expo build 3. Using native browser or QR code, connect to https://react-app.walletconnect.com/ 4. Using native browser or QR code, connect to https://wagmi-app.vercel.app/ 5. In the wallet, go to settings, experimental, wallet connect, and check that you have these two sessions 6. Using native browser or QR code, connect to https://rainbowkit.com/ (using WC) 7. In the wallet, go to settings, experimental, wallet connect, and check that you only have 2 sessions with the first one you connected no longer in the list of active sessions ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** https://github.com/user-attachments/assets/703ab57f-5dee-4889-957e-9b7e98b02d80 ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds automatic disconnection of an existing WalletConnect v2 session when new approvals push the active session count over a configured limit, which could unexpectedly drop a user’s oldest connection. Logic is small and covered by unit tests, but it affects live connection management. > > **Overview** > **Enforces a WalletConnect v2 session cap.** After approving a new session, `WC2Manager` now calls `enforceSessionLimit()` to ensure active sessions stay under `AppConstants.WALLET_CONNECT.LIMIT_SESSIONS` by disconnecting the *oldest* session (based on smallest `expiry`). > > **Adds test coverage** for the new behavior, including cases where the limit is exceeded (oldest session is disconnected) and where the session count is at/under the limit (no disconnections). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit bf6c451. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** This PR downgrades `@tanstack/react-query` to `^4.43.0` to allow us to bring in shared tooling between extension and mobile for queries. This is a temporary measure until the extension gets to React 18, which is the minimum version for `@tanstack/react-query@5`. Most of the breaking changes that impact our existing code is type related due to `TError` being `unknown` in v4. Additionally a couple of properties we use have been renamed between the two versions. The semantics of `isLoading` has also changed for `enabled: false` queries, but that can be recovered by using `isFetching && isLoading` This PR adjusts all of these. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** https://consensyssoftware.atlassian.net/browse/WPC-445 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Moderate risk because it downgrades a core data-fetching library and adjusts loading/polling semantics across multiple hooks, which could change UI states or caching behavior if any edge cases were missed. > > **Overview** > Downgrades `@tanstack/react-query` from v5 to v4 (including lockfile updates) and updates the app’s `QueryClient` defaults to use v4’s `cacheTime` option. > > Aligns hooks and tests with v4 API/behavior changes: replaces `gcTime` usage, updates `keepPreviousData` configuration, switches some status checks from `isPending` to `isLoading`, adds/propagates `isFetching`, and standardizes derived `isLoading` as `isLoading && isFetching` for disabled/manual queries (Card hooks, Ramp hooks, Predict query options, and related tests). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit cb3d691. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
… signing (#28001) ## **Description** ~2% of Claim/Withdraw transactions are failing with Safe GS026 or GS013 errors. Root cause: `EthQuery` caches query results for up to 15 minutes, which can serve a stale nonce when signing Safe transactions. This fix calls `invalidateQueryCache` before `prepareClaim` and `signWithdraw` so the Safe TX is always signed with a fresh nonce. ## **Changelog** CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/PRED-769 ## **Manual testing steps** ```gherkin Feature: Predict Claim/Withdraw nonce freshness Scenario: User claims positions without GS026/GS013 errors Given the user has claimable positions on Predict And the EthQuery cache contains a stale nonce When the user initiates a Claim transaction Then the query cache is invalidated before signing the Safe TX And the transaction succeeds without GS026 or GS013 errors Scenario: User withdraws funds without GS026/GS013 errors Given the user has a balance available for withdrawal on Predict And the EthQuery cache contains a stale nonce When the user initiates a Withdraw transaction Then the query cache is invalidated before signing the Safe TX And the transaction succeeds without GS026 or GS013 errors ``` ## **Screenshots/Recordings** <!-- Not applicable — no UI changes --> ### **Before** <!-- N/A --> ### **After** <!-- N/A --> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches Predict claim/withdraw signing flow; while the change is small, it alters pre-sign network behavior and could impact transaction submission timing or error handling. > > **Overview** > Prevents stale Safe nonces during Predict **Claim** and **Withdraw** by invalidating the underlying `EthQuery` cache immediately before `prepareClaim` and `signWithdraw` run. > > This adds an `invalidateQueryCache` call (via `blockTracker.checkForLatestBlock()`) to force fresh chain state before signing, reducing intermittent Safe `GS026/GS013` failures. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 2144104. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
…27944) ## Description `removeSession()` revokes permissions via `permissionsController.revokeAllPermissions()` after cleanup, but `removeAll()` did not — leaving stale permission entries in the `PermissionController` for every removed session. This adds the missing `revokeAllPermissions(session.pairingTopic)` call in `removeAll()`. ### Note on permission key inconsistency This PR uses `session.pairingTopic` as the revocation key because that's the key permissions are created under (`origin: channelId` where `channelId = pairingTopic` — see `_handleSessionProposal`). However, `removeSession()` uses `session.topic` instead (line 377), which is a different value. That call is likely a no-op (silently caught by the surrounding try/catch). This is a pre-existing bug in `removeSession` — it should probably also use `session.pairingTopic`. Left as-is to keep this PR scoped. ## Related issues Follow-up to #27932 ## Manual testing steps 1. Connect multiple dapps via WalletConnect 2. Go to Settings > Experimental > WalletConnect Sessions 3. Verify sessions are listed 4. Trigger `removeAll()` (e.g., via wallet reset flow) 5. Verify permission entries for those sessions are no longer in `PermissionController.state.subjects` ## Pre-merge author checklist - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: small, well-scoped change that only adds missing permission cleanup during `WC2Manager.removeAll()`, with a unit test covering the new behavior. > > **Overview** > Fixes `WC2Manager.removeAll()` to also revoke WalletConnect v2 permissions for each active session (via `PermissionController.revokeAllPermissions(session.pairingTopic)`) before disconnecting, preventing stale permission entries after bulk session removal. > > Adds a focused unit test asserting permission revocation is triggered during `removeAll()`, and logs (without failing) if revocation throws. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e8b281e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Jiexi Luan <jiexiluan@gmail.com>
…it price (#27907) ## **Description** Limit price presets (Mid, Bid, Ask, -1%, -2%) hardcoded `formatWithSignificantDigits(value, 4)` — 4 significant digits. For XRP-range prices (~$2.34), this truncated values to 3 decimal places ($2.342) instead of the expected 4 ($2.3418). Fixed by using `DECIMAL_PRECISION_CONFIG.MaxSignificantFigures` (= 5), matching the HyperLiquid API limit and the `PRICE_RANGES_UNIVERSAL` display config. Also added testIDs to all preset buttons for automated testing. ## **Changelog** CHANGELOG entry: Fixed limit price preset buttons (Mid, Bid, Ask, percentage) truncating one decimal place for low-price assets like XRP ## **Related issues** Fixes: [TAT-2399](https://consensyssoftware.atlassian.net/browse/TAT-2399) ## **Manual testing steps** ```gherkin Feature: Limit price preset decimal precision Scenario: Mid preset shows correct decimals for XRP Given I am on the XRP Long Limit order screen When I open the limit price bottom sheet And I press the Mid preset button Then the limit price shows 4 decimal places (e.g., $2.3418) Scenario: All presets show correct decimals Given I am on the XRP Long Limit order screen When I press each preset (Mid, Bid, -1%, -2%) Then each preset value has 4 decimal places Scenario: Ask preset works for short orders Given I am on the XRP Short Limit order screen When I press the Ask preset button Then the limit price shows 4 decimal places ``` ## **Screenshots/Recordings** ### **Before** Bug confirmed via CDP eval: `formatWithSignificantDigits(2.3418, 4)` → `2.342` (3 decimals instead of 4) ### **After** https://github.com/user-attachments/assets/72b3617e-afdf-49c1-bbbb-2b96e176668d ## **Validation Recipe** <details> <summary>Automated validation recipe (validate-recipe.sh)</summary> ```json { "pr": "27907", "title": "Limit price presets use correct decimal precision (5 sig figs)", "jira": "TAT-2399", "acceptance_criteria": [ "Tapping any limit price preset populates the value with market-correct decimal precision", "Validated on XRP (reported case) and SOL (different price range)", "All five presets covered: Mid, Bid, Ask, -1%, -2%", "No regression to manual limit price entry" ], "validate": { "static": ["yarn lint:tsc"], "runtime": { "pre_conditions": ["wallet.unlocked", "perps.feature_enabled"], "steps": [ {"id": "nav_xrp", "description": "Navigate to XRP market details", "action": "flow_ref", "ref": "market-discovery", "params": {"symbol": "XRP"}}, {"id": "press_long", "action": "press", "test_id": "perps-market-details-long-button"}, {"id": "wait_form", "action": "wait_for", "test_id": "perps-order-header-order-type-button"}, {"id": "press_order_type", "action": "press", "test_id": "perps-order-header-order-type-button"}, {"id": "wait_type_sheet", "action": "wait_for", "test_id": "perps-order-type-limit"}, {"id": "press_limit", "action": "press", "test_id": "perps-order-type-limit"}, {"id": "wait_limit_form", "action": "wait_for", "test_id": "perps-order-view-limit-price-row"}, {"id": "press_price_row", "action": "press", "test_id": "perps-order-view-limit-price-row"}, {"id": "wait_price_sheet", "action": "wait_for", "test_id": "keypad-delete-button"}, {"id": "press_mid_xrp", "action": "press", "test_id": "perps-limit-price-preset-mid"}, {"id": "wait_mid", "action": "wait", "ms": 500}, {"id": "check_mid_xrp", "description": "Assert Mid preset >= 4 decimals for XRP", "action": "eval_sync", "expression": "...", "assert": {"operator": "gt", "field": "decimals", "value": 3}}, {"id": "press_bid_xrp", "action": "press", "test_id": "perps-limit-price-preset-bid"}, {"id": "check_bid_xrp", "description": "Assert Bid >= 4 decimals", "action": "eval_sync", "assert": {"operator": "gt", "field": "decimals", "value": 3}}, {"id": "press_pct_minus1_xrp", "action": "press", "test_id": "perps-limit-price-preset--1"}, {"id": "check_pct_minus1_xrp", "description": "Assert -1% >= 4 decimals", "action": "eval_sync", "assert": {"operator": "gt", "field": "decimals", "value": 3}}, {"id": "press_pct_minus2_xrp", "action": "press", "test_id": "perps-limit-price-preset--2"}, {"id": "check_pct_minus2_xrp", "description": "Assert -2% >= 4 decimals", "action": "eval_sync", "assert": {"operator": "gt", "field": "decimals", "value": 3}}, {"id": "nav_sol", "action": "flow_ref", "ref": "market-discovery", "params": {"symbol": "SOL"}}, {"id": "check_mid_sol", "description": "SOL no-regression check", "action": "eval_sync", "assert": {"operator": "eq", "field": "isValid", "value": true}}, {"id": "nav_short_xrp", "action": "flow_ref", "ref": "market-discovery", "params": {"symbol": "XRP"}}, {"id": "check_ask_xrp", "description": "Assert Ask >= 4 decimals", "action": "eval_sync", "assert": {"operator": "gt", "field": "decimals", "value": 3}} ] } } } ``` Full recipe: `.task/fix/tat-2399-0325-1840/artifacts/recipe.json` </details> ## **Pre-merge author checklist** - [x] I've followed MetaMask Contributor Docs and Coding Standards - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [ ] I've documented my code using JSDoc format if applicable - [x] I've applied the right labels on the PR ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches perps limit order price entry; while the change is small, it affects how preset prices are computed and could impact order placement values if incorrect. > > **Overview** > Fixes limit-price preset buttons (Mid/Bid/Ask and +/- % presets) to format using `DECIMAL_PRECISION_CONFIG.MaxSignificantFigures` instead of hardcoded 4 significant digits, preventing truncation for low-priced assets (e.g., XRP). > > Adds `testID`s for each preset button (including dynamic % presets) and extends `PerpsLimitPriceBottomSheet` tests to assert the correct decimal precision for XRP-range prices. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit ecb689b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )